package com.pkk.penguin.frame.know.boot.config.common.handle;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.pkk.penguin.frame.know.boot.bo.hbsl.MessageType;
import com.pkk.penguin.frame.know.boot.constand.WebSocketType;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

/**
 * @description: 抽象的web长连接
 * @author: peikunkun
 * @create: 2020-05-20 11:03
 **/
@Slf4j
public abstract class AbstractWebSocketServer<T extends AbstractWebSocketServer> implements WebSocketServeInterface {


  /**
   * concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象[不同功能的连接WebSocketType,客户端码,]
   */
  public static final ConcurrentHashMap<WebSocketType, ConcurrentHashMap<String, AbstractWebSocketServer>> WEB_SOCKET_MAP = new ConcurrentHashMap<>();

  /**
   * 用来记录当前在线连接数
   */
  private static ConcurrentHashMap<WebSocketType, AtomicInteger> onlineCount = new ConcurrentHashMap<>();


  /** 与某个客户端的连接会话，需要通过它来给客户端发送数据 */
  private Session session;
  /** 接收clientCode */
  private String clientCode;


  /**
   * @Description: 获取子对象
   * @Param: session
   * @Param clientCode
   * @return: T
   * @Author: peikunkun
   * @Date: 2020/5/20 0020 上午 11:55
   */
  public AbstractWebSocketServer webSocketServer(Session session, String clientCode) {
    this.session = session;
    this.clientCode = clientCode;
    return this;
  }


  /**
   * @Description: 获取不同功能的websocket
   * @Param: type
   * @return: java.util.concurrent.ConcurrentHashMap<java.lang.String, T>
   * @Author: peikunkun
   * @Date: 2020/5/20 0020 上午 11:21
   */
  public ConcurrentHashMap<String, AbstractWebSocketServer> getWebSocket(WebSocketType type) {
    return WEB_SOCKET_MAP.get(type);
  }


  /**
   * 获取当前记数数
   *
   * @return
   */
  public synchronized int getOnlineCount() {
    return getOnlineCountAtomic().get();
  }


  private synchronized AtomicInteger getOnlineCountAtomic() {
    final WebSocketType webSocketType = getWebSocketType();
    final AtomicInteger atomicInteger = onlineCount.get(webSocketType);
    if (null == atomicInteger) {
      onlineCount.put(webSocketType, new AtomicInteger(0));
    }
    return onlineCount.get(webSocketType);
  }


  /**
   * 增加记数
   */
  public synchronized void addOnlineCount() {
    getOnlineCountAtomic().addAndGet(1);
  }

  /**
   * 减少记数
   */
  public synchronized void subOnlineCount() {
    getOnlineCountAtomic().addAndGet(-1);
  }


  /**
   * @Description: 获取websocket的类型
   * @return: com.pkk.penguin.frame.know.boot.constand.WebSocketType
   * @Author: peikunkun
   * @Date: 2020/5/20 0020 上午 11:27
   */
  public abstract WebSocketType getWebSocketType();


  /**
   * @Description: 获取客户端链接信息
   * @return: java.util.concurrent.ConcurrentHashMap<java.lang.String, T>
   * @Author: peikunkun
   * @Date: 2020/5/20 0020 下午 1:15
   */
  public ConcurrentHashMap<String, AbstractWebSocketServer> getWebSocket() {
    ConcurrentHashMap<String, AbstractWebSocketServer> webSocket = WEB_SOCKET_MAP.get(getWebSocketType());
    if (null == webSocket) {
      WEB_SOCKET_MAP.put(getWebSocketType(), new ConcurrentHashMap<>());
    }
    return WEB_SOCKET_MAP.get(getWebSocketType());
  }


  /**
   * 连接建立成功调用的方法
   */
  @OnOpen
  public void onOpen(Session session, @PathParam("clientCode") String clientCode) throws IOException {
    final ConcurrentHashMap<String, AbstractWebSocketServer> webSocket = getWebSocket();
    if (webSocket.containsKey(clientCode)) {
      webSocket.remove(clientCode);
      webSocket.put(clientCode, webSocketServer(session, clientCode));
      //加入set中
    } else {
      webSocket.put(clientCode, webSocketServer(session, clientCode));
      //加入set中
      addOnlineCount();
      //在线数加1
    }
    try {
      sendMessage(session, "[" + clientCode + "]连接成功");
    } catch (IOException e) {
      sendMessage(session, "[" + clientCode + "]连接失败");
    }
  }

  /**
   * 连接关闭调用的方法
   */
  @OnClose
  public void onClose(Session session, @PathParam("clientCode") String clientCode) throws IOException {
    final ConcurrentHashMap<String, AbstractWebSocketServer> webSocket = getWebSocket();
    if (webSocket.containsKey(clientCode)) {
      webSocket.remove(clientCode);
      //从set中删除
      subOnlineCount();
    }
  }

  /**
   * 收到客户端消息后调用的方法
   *
   * @param message 客户端发送过来的消息
   */
  @OnMessage
  public void onMessage(Session session, @PathParam("clientCode") String clientCode, String message) {
    //可以群发消息
    //消息保存到数据库、redis
    if (StringUtils.isNotBlank(message)) {
      try {
        final ConcurrentHashMap<String, AbstractWebSocketServer> webSocket = getWebSocket();
        //解析发送的报文
        JSONObject jsonObject = JSON.parseObject(message);
        //追加发送人(防止串改)
        jsonObject.put("fromclientCode", clientCode);
        String toclientCode = jsonObject.getString("toclientCode");
        //传送给对应toclientCode用户的websocket
        if (StringUtils.isNotBlank(toclientCode) && webSocket.containsKey(toclientCode)) {
          sendMessage(session, jsonObject.toJSONString());
        } else {
          log.error("请求的clientCode:" + toclientCode + "不在该服务器上");
          //否则不在这个服务器上，发送到mysql或者redis
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * @param session
   * @param error
   */
  @OnError
  public void onError(Session session, @PathParam("clientCode") String clientCode, Throwable error) throws IOException {
    sendMessage(session, "[" + clientCode + "]操作异常,,原因:" + error.getMessage());
    log.error("用户错误:" + clientCode + ",原因:" + error.getMessage());
    error.printStackTrace();
  }


  /**
   * 发送自定义消息
   */
  public void sendInfo(String message, @PathParam("clientCode") String clientCode) throws IOException {
    log.info("发送消息到:" + clientCode + "，报文:" + message);
    final ConcurrentHashMap<String, AbstractWebSocketServer> webSocket = getWebSocket(this.getWebSocketType());
    if (StringUtils.isNotBlank(clientCode) && webSocket.containsKey(clientCode)) {
      final AbstractWebSocketServer abstractWebSocketServer = webSocket.get(clientCode);
      sendMessage(abstractWebSocketServer.session, message);
    } else {
      log.error("用户" + clientCode + ",不在线！");
    }
  }


  /**
   * @Description: 构建发送时间
   * @Param: message
   * @return: com.alibaba.fastjson.JSONObject
   * @Author: peikunkun
   * @Date: 2020/5/13 0013 下午 4:42
   */
  public synchronized String buildSendTime(JSONObject message) {
    final JSONObject msgJSON = (JSONObject) message.clone();
    if (msgJSON.containsKey("msg")) {
      final String msg = msgJSON.getString("msg");
      msgJSON.put("msg", "[" + DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN) + "]" + "\n" + msg);
    }
    return msgJSON.toJSONString();
  }


  /**
   * 实现服务器主动推送
   */
  public void sendMessage(Session session, String message) throws IOException {
    session.getBasicRemote().sendText(buildSendTime(WebScoketUtil.buildJSON(MessageType.CONNECTION, message)));
  }

  /**
   * 实现服务器主动推送
   */
  public void sendMessage(Session session, JSONObject message) {
    try {
      session.getBasicRemote().sendText(buildSendTime(message));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }


  /**
   * @author peikunkun<br>
   * @version V1.0<br>
   * @Title: websocker的工具类<br>
   * @Description: <><br>
   * @date 2020/5/13 0013 下午 1:11<br>
   * @URL:[]
   */
  public static class WebScoketUtil {


    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    public static <T extends AbstractWebSocketServer> void broadcostOperate(ConcurrentHashMap<String, T> webSocketMap,
        String message) {
      broadcost(webSocketMap, buildJSON(MessageType.OPERATE, message));
    }

    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    public static <T extends AbstractWebSocketServer> void broadcostConnection(
        ConcurrentHashMap<String, T> webSocketMap,
        String message) {
      broadcost(webSocketMap, buildJSON(MessageType.CONNECTION, message));
    }

    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    public static <T extends AbstractWebSocketServer> void broadcostOnline(ConcurrentHashMap<String, T> webSocketMap,
        String message) {
      broadcost(webSocketMap, buildJSON(MessageType.ONLINE, message));
    }


    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    public static <T extends AbstractWebSocketServer> void broadcost(ConcurrentHashMap<String, T> webSocketMap,
        MessageType type, String message) {
      broadcost(webSocketMap, buildJSON(type, message));
    }

    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    public static <T extends AbstractWebSocketServer> void broadcost(ConcurrentHashMap<String, T> webSocketMap,
        MessageType type,
        JSONObject message) {
      message.put("type", type.getType());
      broadcost(webSocketMap, message);
    }

    /**
     * @Description:广播消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:13
     */
    private static <T extends AbstractWebSocketServer> void broadcost(ConcurrentHashMap<String, T> webSocketMap,
        JSONObject message) {
      final Iterator<String> iterator = webSocketMap.keySet().iterator();
      while (iterator.hasNext()) {
        final String clientCode = iterator.next();
        final AbstractWebSocketServer webSocketServer = webSocketMap.get(clientCode);
        webSocketMap.get(clientCode).sendMessage(webSocketServer.session, message);
      }
    }


    /**
     * @Description: 单独发送消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:15
     */
    public static <T extends AbstractWebSocketServer> void send(ConcurrentHashMap<String, T> webSocketMap,
        MessageType type,
        JSONObject message, String... clientCodes) {
      for (String clientCode : clientCodes) {
        if (!webSocketMap.containsKey(clientCode)) {
          continue;
        }
        message.put("type", type.getType());
        final AbstractWebSocketServer webSocketServer = webSocketMap.get(clientCode);
        webSocketMap.get(clientCode).sendMessage(webSocketServer.session, message);
      }
    }

    /**
     * @Description: 单独发送消息
     * @Param: message
     * @return: void
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:15
     */
    public static <T extends AbstractWebSocketServer> void send(ConcurrentHashMap<String, T> webSocketMap,
        MessageType type, String message,
        String... clientCodes) {
      send(webSocketMap, type, buildJSON(type, message), clientCodes);
    }


    /**
     * @Description: 获取客户端信息
     * @Param: clientCodes
     * @return: java.util.Map<java.lang.String, com.pkk.penguin.frame.know.boot.config.handle.hbsl.HbslWebSocketServer>
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:22
     */
    public static <T extends AbstractWebSocketServer> Map<String, AbstractWebSocketServer> get(
        ConcurrentHashMap<String, T> webSocketMap,
        String... clientCodes) {
      final HashMap<String, AbstractWebSocketServer> clientCodeMap = new HashMap<>(clientCodes.length);
      for (String clientCode : clientCodes) {
        clientCodeMap.put(clientCode, webSocketMap.get(clientCode));
      }
      return clientCodeMap;
    }


    /**
     * @Description:构建json
     * @Param: message
     * @return: com.alibaba.fastjson.JSONObject
     * @Author: peikunkun
     * @Date: 2020/5/13 0013 下午 1:25
     */
    public static JSONObject buildJSON(MessageType type, String message) {
      JSONObject json = new JSONObject();
      json.put("type", type.getType());
      json.put("msg", message);
      return json;
    }
  }

}
